package com.jzsqdy.mycat;

import java.security.SecureRandom;
import java.util.Random;


/**
 * 生成字母和数字组成的编码
 *
 * @author chenjf@hzzh.com
 * @version 3.0.0
 * @since 3.0.0
 */
public class CodeGenerator {

    /**
     * Used to limit UIDs to unsigned longs
     */
    public static final int MAX_WIDTH = 7;
    /**
     * Use the SecureRandom class to avoid blocking calls
     */
    private static SecureRandom random_generator = new SecureRandom(fromLong(System.currentTimeMillis()));
    private static String[] arrays = new String[] {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c",
        "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x",
        "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S",
        "T", "U", "V", "W", "X", "Y", "Z"};

    private static Random rand = new Random();

    /**
     * 生成随机ID .
     * <p>
     * 由大小写的26个字母和数字组成
     * <p>
     * 10万级数据量建议程度在7以上
     * <p>
     * 百万级建议在8以上
     * <p>
     * mysql一般情形下不区分字母大小写，需要修改核对规则(collate);
     * <p>
     * create table some_table(
     * <p>
     * str char(20) binary
     * <p>
     * )
     *
     * @param len ID长度
     * @return String
     * @author chenjf@hzzh.com
     */
    public static String generate(int len) {
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < len; i++) {
            buffer.append(arrays[rand.nextInt(arrays.length)]);
        }
        return buffer.toString();
    }

    /**
     * Get the next random UID. It creates random bytes, then convert it to an
     * unsigned long.
     *
     * @param width Number of bytes to randomize, it can not be larger than
     *              {@link MAX_WIDTH} bytes wide
     * @return a randomly UID
     * @throws throws IllegalArgumentException if the width is larger than
     *                {@link MAX_WIDTH} bytes
     */
    public static long getRandomUID(final int width) {
        if (width > MAX_WIDTH) {
            throw new IllegalArgumentException("Expecting to return an unsigned long "
                + "random integer, it can not be larger than " + MAX_WIDTH + " bytes wide");
        }
        final byte[] bytes = new byte[width];
        random_generator.nextBytes(bytes);

        long value = 0;
        for (int i = 0; i < bytes.length; i++) {
            value <<= 8;
            value |= bytes[i] & 0xFF;
        }

        // make sure we never return 0 as a UID
        return value != 0 ? value : value + 1;
    }

    /**
     * Writes a big-endian 8-byte long at an offset in the given array.
     *
     * @param b      The array to write to.
     * @param offset The offset in the array to start writing at.
     * @throws IndexOutOfBoundsException if the byte array is too small.
     */
    public static void setLong(final byte[] b, final long n, final int offset) {
        b[offset + 0] = (byte) (n >>> 56);
        b[offset + 1] = (byte) (n >>> 48);
        b[offset + 2] = (byte) (n >>> 40);
        b[offset + 3] = (byte) (n >>> 32);
        b[offset + 4] = (byte) (n >>> 24);
        b[offset + 5] = (byte) (n >>> 16);
        b[offset + 6] = (byte) (n >>> 8);
        b[offset + 7] = (byte) (n >>> 0);
    }

    /**
     * Creates a new byte array containing a big-endian 8-byte long integer.
     *
     * @param n A long integer.
     * @return A new byte array containing the given value.
     */
    public static byte[] fromLong(final long n) {
        final byte[] b = new byte[8];
        setLong(b, n);
        return b;
    }

    /**
     * Writes a big-endian 8-byte long at the begining of the given array.
     *
     * @param b The array to write to.
     * @param n A long integer.
     * @throws IndexOutOfBoundsException if the byte array is too small.
     */
    public static void setLong(final byte[] b, final long n) {
        setLong(b, n, 0);
    }

}
